import crypto from "node:crypto";
import jwt from "jsonwebtoken";
import svgCaptcha from "svg-captcha";
import TemplatePort from "../template.js";
import { verify, keys } from "./verify.js";
import { TimeUtils } from "../../utils/utils.js";
import config from "../../config/config.js";

class User extends TemplatePort {
  constructor(validator, verifyKeys) {
    super(validator, false, verifyKeys);
  }

  async verifyCode(ip, code, email) {
    let vcode = await this.store.vcode.findOne({ ip });
    if (vcode) {
      if (vcode.code != code) throw new Error("验证码错误");
      if (email && vcode.email != email) throw new Error("邮箱不匹配");
      if (vcode.expire < Date.now()) {
        await this.store.vcode.deleteOne({ ip });
        throw new Error("验证码已过期, 请重新获取");
      }
    } else throw new Error("验证码不存在, 请重新获取");
  }

  /**
   * @api {post} /user/logup 注册
   * @apiDescription 注册接口
   * @apiGroup 用户
   * @apiBody {String{2-10}} name 用户名
   * @apiBody {String{5-10}} username 账户
   * @apiBody {String} password 密码，前端需要哈希加密
   * @apiBody {String} email 邮箱
   * @apiBody {String} code 邮箱验证码
   * @apiUse templatePort
   */
  async logup(req) {
    let data = req.body;
    let ip = req.get("host");
    this.selValidator(this.pkeys.logup).toVerify(data);

    // 是否已注册username
    let user = await this.store.user.findOne({ username: data.username });
    if (user) throw new Error(`账户：${data.username} 已注册`);

    // 验证码验证
    await this.verifyCode(ip, data.code, data.email);

    // 注册用户信息
    delete data.code;
    let id = await this.store.user.insertOne(data);
    await this.store.vcode.deleteOne({ ip });
    return this.resT(null, "注册成功");
  }

  /**
   * @api {get} /user/captcha 验证码图片
   * @apiDescription 获取验证码图片
   * @apiGroup 用户
   * @apiSuccess {String} url 验证码图片地址，直接给img标签使用即可
   */
  async captcha(req, res) {
    const captcha = svgCaptcha.create(config.captcha);
    let { text, data } = captcha;
    let ip = req.get("host");

    await this.store.vcode.updateOne(
      { ip },
      {
        ip,
        code: text,
        expire: Date.now() + config.mailExpire,
      },
      true
    );

    res.set("Content-Type", "image/svg+xml");
    res.status(200).send(data);
  }

  /**
   * @api {post} /user/login 登录
   * @apiDescription 登录接口
   * @apiGroup 用户
   * @apiBody {String{5-10}} username 账户
   * @apiBody {String} password 密码，前端需要哈希加密
   * @apiBody {String} code 验证码
   * @apiSuccess {String} user_id 用户id
   * @apiSuccess {String} publicKey 加密公钥
   * @apiUse templatePort
   */
  async login(req, res) {
    let data = req.body;
    let ip = req.get("host");
    let loginTime = TimeUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
    this.selValidator(this.pkeys.login).toVerify(data);

    // 登录验证
    await this.verifyCode(ip, data.code);
    let user = await this.store.user.findOne({ username: data.username });
    if (!user) throw new Error("用户不存在");
    if (user.password != data.password) throw new Error("密码错误");

    // 登录成功，设置token
    let user_id = user._id.toHexString();
    const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
      modulusLength: 1024,
      publicKeyEncoding: { type: "spki", format: "pem" },
      privateKeyEncoding: { type: "pkcs8", format: "pem" },
    });
    const token = jwt.sign({ user_id }, config.jwt);

    // 保存token信息，token分表便于扩展单点登录
    await this.store.token.updateOne(
      { user_id },
      {
        user_id,
        publicKey,
        privateKey,
        expire: Date.now() + config.tokenExpire,
      },
      true
    );
    await this.store.user.updateOne(
      { _id: user._id },
      { loginTime, logoutTime: "0000-00-00 00:00:00" }
    );

    // 登录成功，返回token信息
    res.cookie("token", token, { httpOnly: true, maxAge: config.tokenExpire });
    await this.store.vcode.deleteOne({ ip });
    return this.resT({ publicKey, user_id }, "登录成功");
  }

  /**
   * @api {get} /user/loginCheck 登录验证
   * @apiDescription 登录验证接口
   * @apiGroup 用户
   * @apiSuccess {String} user_id 用户id
   * @apiSuccess {String} publicKey 加密公钥
   * @apiUse templatePort
   */
  async loginCheck(req, res, next) {
    // 作为中间件，对于不需要token的接口直接放行
    if (next && config.withoutToken.includes(req.path)) return next();

    let token = req.cookies?.token;
    if (!token) {
      res.status(401).send({ msg: "未登录" });
      return;
    }

    let info = jwt.verify(token, config.jwt);
    let tokenInfo = await this.store.token.findOne({ user_id: info.user_id });
    let { publicKey, user_id, expire } = tokenInfo || {};
    if (!user_id || expire < Date.now()) {
      res.clearCookie("token");
      throw new Error("登录已过期，请重新登录");
    }

    if (next) next();
    else return this.resT({ publicKey, user_id }, "已登录");
  }

  /**
   * @api {get} /user/logout 登出
   * @apiDescription 登出接口
   * @apiGroup 用户
   * @apiUse templatePort
   */
  async logout(req, res) {
    let token = req.cookies?.token;
    if (!token) throw new Error("未登录");
    let info = jwt.verify(token, config.jwt);
    await this.store.token.deleteOne({ user_id: info.user_id });
    res.clearCookie("token");
    return this.resT(null, "登出成功");
  }

  /**
   * @api {post} /user/update 修改
   * @apiDescription 修改接口
   * @apiGroup 用户
   * @apiBody {String} id 用户id
   * @apiBody {String} name 姓名
   * @apiBody {String} username 账户名
   * @apiBody {String} password 密码，前端需要哈希加密
   * @apiBody {String} email 邮箱
   * @apiBody {String} code 验证码
   * @apiUse templatePort
   */
  async update(req, res) {
    let data = req.body;
    let ip = req.get("host");
    this.selValidator(this.pkeys.update).toVerify(data);
    let user = await this.store.user.findOne({ id: data.id });
    if (!user) throw new Error("用户不存在");

    // 验证码验证
    await this.verifyCode(ip, data.code, data.email);

    let writeData = {
      name: data.name,
      username: data.username,
      password: data.password,
      email: data.email,
    };
    await this.store.user.updateOne({ _id: user._id }, writeData);
    await this.store.vcode.deleteOne({ ip });

    let token = req.cookies?.token;
    let info = jwt.verify(token, config.jwt);
    if (data.id == info.user_id) {
      await this.store.token.deleteOne({ user_id: info.user_id });
      res.clearCookie("token");
      return this.resT(null, "更新成功，请重新登录");
    }

    return this.resT(null, "更新成功");
  }

  /**
   * @api {post} /user/list 用户列表
   * @apiDescription 用户列表接口
   * @apiGroup 用户
   * @apiBody {Number} page=1 页码
   * @apiBody {Number} size=10 每页数量
   * @apiBody {String} [sortkey] 排序字段
   * @apiBody {Number=1,-1} [sortway] 排序方式，1升序，-1降序
   * @apiBody {String} [name] 姓名
   * @apiBody {String} [username] 账户名
   * @apiBody {String} [email] 邮箱
   * @apiSuccess {Number} page 页码
   * @apiSuccess {Number} size 每页数量
   * @apiSuccess {Number} total 总数量
   * @apiSuccess {Array} data 用户列表
   * @apiSuccess {Object} data.0 用户信息
   * @apiSuccess {String} data.0._id 用户ID
   * @apiSuccess {String} data.0.name 姓名
   * @apiSuccess {String} data.0.username 账户名
   * @apiSuccess {String} data.0.email 邮箱
   * @apiSuccess {String} data.0.loginTime 登录时间
   * @apiSuccess {String} data.0.logoutTime 登出时间
   * @apiSuccess {String} data.0.create_time 注册时间
   * @apiSuccess {String} data.0.update_time 修改时间
   * @apiUse templatePort
   */
  async list(req) {
    let data = req.body;
    let { page = 1, size = 10 } = data || {};
    let sort = null;
    if (data.sortkey) {
      sort = {};
      sort[data.sortkey] = data.sortway || 1;
    }
    this.selValidator(this.pkeys.list).toVerify(data, false);

    delete data.page;
    delete data.size;
    delete data.sortkey;
    delete data.sortway;
    let { list, total } = await this.store.user.findSortPage(
      data,
      sort,
      (page - 1) * size,
      size
    );
    list = list.map((item) => {
      delete item.password;
      return item;
    });

    return this.pageT(list, page, size, total, "查询成功");
  }

  /**
   * @api {get} /user/delete 删除用户
   * @apiDescription 删除用户接口
   * @apiGroup 用户
   * @apiQuery {String} id 用户ID
   * @apiUse templatePort
   */
  async delete(req) {
    let token = req.cookies?.token;
    let { id } = req.query || {};
    let info = jwt.verify(token, config.jwt);
    if (!id) throw new Error("缺少参数id");
    if (info.user_id === id) throw new Error("不能删除自己");
    await this.store.user.deleteOne({ _id: id });
    return this.resT(null, "删除成功");
  }

  /**
   * @api {get} /user/getUserById id查询
   * @apiDescription 获取用户信息接口
   * @apiGroup 用户
   * @apiQuery {String} id 用户ID
   * @apiSuccess {String} _id 用户ID
   * @apiSuccess {String} name 姓名
   * @apiSuccess {String} username 账户名
   * @apiSuccess {String} email 邮箱
   * @apiSuccess {String} loginTime 登录时间
   * @apiSuccess {String} logoutTime 登出时间
   * @apiSuccess {String} create_time 注册时间
   * @apiSuccess {String} update_time 修改时间
   * @apiUse templatePort
   */
  async findById(req) {
    let { id } = req.query || {};
    if (!id) throw new Error("缺少参数id");
    let user = await this.store.user.findOne({ id });
    if (!user) throw new Error("用户不存在");
    delete user.password;
    return this.resT(user, "查询成功");
  }
}

export default new User(verify, keys);
